/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#ifndef CONFIGREADER_H
#define CONFIGREADER_H

#include "SimInterface/ISimulator.h"

#include <iosfwd>
#include <string>
#include <stack>
#include <boost/smart_ptr.hpp>

// Przestrzeń nazw
namespace RafMat
{
namespace SimFile
{

class ConfigReader;
class BlockInfo;
typedef boost::shared_ptr<BlockInfo> BlockInfoSPtr;

/**
    Klasa wyjątków dla czytnika konfiguracji.
*/
class conf_reader_error : public std::logic_error
{
public:
    /**
        Konstruktor przypisujący dowolną wiadomość.
        \param [in] __arg Wiadomość przypisana bieżącej instancji.
    */
    explicit conf_reader_error(const std::string& __arg);

    static conf_reader_error syntaxError(int line, const std::string& why);
    static conf_reader_error semanticError(const BlockInfo& blk, const std::string& why);
    static conf_reader_error unbalansedStack(int line, const std::string& blockName, const std::string& expected);
    static conf_reader_error unknownAssingment(const BlockInfo& malfunctBlock, const std::string& assId);
};

/**
    Przechowuje informację o bloku konfiguracyjnym.
    Blok składa się z podbloków (wektor) oraz przypisań.

    Blok nie przechowuje wskaźnika do bloku rodzica w celu uniknięcia
    zależności cyklicznych.
*/
class BlockInfo
{
    // Stwórca bloków ConfigReader ma pełen dostęp do ich pól.
    friend class ConfigReader;

public:
    typedef std::string String;
    typedef std::map<String, String> AssignmentMap;
    typedef std::vector<BlockInfoSPtr> BlockContainer;

    /**
        Tworzy blok o identyfikatorze \c id i \c nazwie name.
    */
    BlockInfo(String id, String name);

    /**
        Zwraca wartość dla danego przypisania.
        Sprawdzana jest obecność wpisu w mapie.
    */
    String operator [] (String assId) const throw(conf_writer_error);

    String id() const { return m_id; }
    String name() const { return m_name; }

    /**
        Zwraca bloki potomne.
    */
    const BlockContainer& children() const { return m_children; }
    const AssignmentMap& assignments() const { return m_assignments; }

private:
    String m_id;
    String m_name;
    AssignmentMap m_assignments;
    BlockContainer m_children;
};

/**
    Klasa realizuje odczyt konfiguracji z pliku.
    Tworzy drzewo bloków. Poprawnością wpisów w drzewie
    zajmuje się użytkownik, tzn. klasa nie sprawdza, czy
    np. wejście Input posiada odpowiednie wpisy jak type, offset, etc.
*/
class ConfigReader
{
public:
    static const char COMMENT_CHAR = ';';
    static const char LBR_CHAR = '{';
    static const char RBR_CHAR = '}';
    static const char BLOCKEND_CHAR = '/';

    ConfigReader(std::istream& input);

    /** Zwraca konfigurację w postaci struktur BlockInfo.

        Zastosowano ładowanie leniwe - pierwsze użycie tej funkcji
        powoduje parsowanie pliku. Każde następne zwraca wcześniej
        utworzone drzewo konfiguracyjne.

        \return Wskaźnik na "korzeń" drzewa.
        \throws conf_reader_error w przypadku błędnych danych.
    */
    BlockInfoSPtr getConfiguration() throw (conf_reader_error);
private:

    static const std::string LBR_END;

    /**
        Ładuje konfigurację ze strumienia.

        \remarks Strumień po ładowaniu nie jest resetowany.
        Kolejne wywołanie funkcji zwróci pustą konfigurację lub błąd.

        \return Wskaźnik na "korzeń" drzewa.
    */
    BlockInfoSPtr loadConfiguration() throw(conf_reader_error);

    /**
        Metoda modyfikuje łańcuch tak by nie zawierał komentarzy
        i zbędnych spacji.

        Treść linii modyfikowana jest przez argument w celu uniknięcia
        zbędnych kopii łańcucha.

        \param [in/out] Treść linii konfiguracji (zostanie zmodyfikowana przez argument!).
    */
    void lexLine(std::string& line);

    /**
        Próbuje parsować daną linię konfiguracji.
        Zakłada się, że komentarze i wiodące spacje zostały wycięte (czyli
        linia jest już po etapie analizy leksykalnej.

        \param [in] line Akutalnie przetwarzana linijka kodu konfiguracji.
        \param [in] currentBlock Aktualnie przetwarzany blok.
        \throws config_reader_error.
    */
    BlockInfoSPtr parseLine(const std::string& line, BlockInfoSPtr currentBlock) throw (conf_reader_error);

    int m_currentLine;                          ///< Aktualnie przetwarzana linia.
    BlockInfoSPtr m_evaluatedConfig;            ///< Wyprowadzone drzewo bloków.
    std::stack<BlockInfoSPtr> m_blockStack;     ///< Stos weryfikujący kolejność bloków.
    std::istream& m_input;                      ///< Strumień wejściowy.
};



}
}

#endif // CONFIGREADER_H
